<script setup>
import { checkUpload, initUpload, mergeUpload, uploadFileInfo } from "~/utils/upload";

import SparkMD5 from 'spark-md5'
import { ref } from "vue";
import { ElMessage } from "element-plus";
import { fileSuffixTypeUtil } from "~/utils/file";
import axios from "axios";

const FILE_UPLOAD_ID_KEY = 'file_upload_id'

const chunkSize = 10 * 1024 * 1024

let currentFileIndex = 0
const FileStatus = {
	wait: '等待上传',
	getMd5: '校验MD5',
	chip: '正在创建序列',
	uploading: '正在上传',
	success: '上传成功',
	error: '上传错误'
}

const changeDisabled = ref(false)
const uploadDisabled = ref(false)
// 上传并发数
const simultaneousUploads = ref(3)
const uploadIdInfo = ref<any>()
const uploadFileList = ref<any[]>([])
const retryList = ref<any[]>([])
const videoPoster = ref<any>()
const imgDataUrl = ref('')

function handler() {
	//判断文件列表是否为空
	if (uploadFileList.value.length === 0) {
		ElMessage('请先选择文件')
		return
	}
	//当前操作文件
	const currentFile = uploadFileList.value[currentFileIndex]

	//更新上传标签
	currentFile.status = FileStatus.getMd5

	//截取封面图片
	//this.ScreenshotVideo(currentFile.raw);

	// 1. 计算文件MD5
	getFileMd5(currentFile.raw, async (md5: string, totalChunks: any) => {

		// 2. 检查是否已上传
		const checkResult: any = await checkFileUploadedByMd5(md5)

		// 确认上传状态
		if (checkResult.code === 1) {
			ElMessage.success(`上传成功，文件地址：${checkResult.data.url}`)
			console.log('文件访问地址：' + checkResult.data.url)
			currentFile.status = FileStatus.success
			currentFile.uploadProgress = 100
			return
		} else if (checkResult.code === 2) {  // "上传中" 状态
			// 获取已上传分片列表
			let chunkUploadedList = checkResult.data
			currentFile.chunkUploadedList = chunkUploadedList
		} else {   // 未上传
			console.log('未上传')
		}

		// 3. 正在创建分片
		//currentFile.status = FileStatus.chip;

		//创建分片
		let fileChunks = createFileChunk(currentFile.raw, chunkSize);

		//重命名文件
		let fileName = getNewFileName(currentFile)

		// 获取文件类型
		//let type = currentFile.name.substring(currentFile.name.lastIndexOf(".") + 1)
		let type = fileSuffixTypeUtil(currentFile.name)

		let param = {
			fileName: fileName,
			fileSize: currentFile.size,
			chunkSize: chunkSize,
			partCount: totalChunks,
			fileMd5: md5,
			contentType: 'application/octet-stream',
			fileType: type,
		}
		// 4. 获取上传url
		let uploadIdInfoResult = await getFileUploadUrls(param)

		debugger

		let uploadIdInfo = uploadIdInfoResult.data.data

		saveFileUploadId(uploadIdInfo.uploadId)

		let uploadUrls = uploadIdInfo.urlList

		$set(currentFile, 'chunkList', [])


		if (uploadUrls !== undefined) {
			if (fileChunks.length !== uploadUrls.length) {
				ElMessage.error('文件分片上传地址获取错误')
				return
			}
		}
		// else if (uploadUrls.length === 1) {
		// 	currentFileIndex++;
		// 	//文件上传成功
		// 	//this.saveFileInfoToDB(currentFile, fileName, uploadIdInfoResult.data.data, md5);

		// 	currentFile.uploadProgress = 100

		// 	currentFile.status = FileStatus.success

		// 	//return;
		// }

		fileChunks.map((chunkItem, index: number) => {
			currentFile.chunkList.push({
				chunkNumber: index + 1,
				chunk: chunkItem,
				uploadUrl: uploadUrls[index],
				progress: 0,
				status: '—'
			})
		})
		let tempFileChunks: any[] = []

		currentFile.chunkList.forEach((item: any) => {
			tempFileChunks.push(item)
		})

		//更新状态
		currentFile.status = FileStatus.uploading

		// 5. 上传
		await uploadChunkBase(tempFileChunks)

		// let imgParam = {
		// 	fileName: screenImg.name,
		// 	fileSize: screenImg.size,
		// 	partCount: 1,
		// 	contentType: 'application/octet-stream',
		// 	fileType: 'image',
		// }

		// //上传封面图
		// let screenImgUrl = await self.getFileUploadUrls(imgParam)


		// 处理分片列表，删除已上传的分片
		tempFileChunks = processUploadChunkList(tempFileChunks)

		console.log('上传完成')


		//判断是否单文件上传或者分片上传
		if (uploadIdInfo.uploadId === "SingleFileUpload") {
			console.log("单文件上传");
			//更新状态
			currentFile.status = FileStatus.success
		}
		else {
			// 6. 合并文件
			const mergeResult: any = await mergeFile({
				uploadId: uploadIdInfo.uploadId,
				fileName: fileName,
				fileMd5: md5,
				fileType: type,
			})

			//合并文件状态
			if (!mergeResult.data) {
				currentFile.status = FileStatus.error
				ElMessage.error(mergeResult.error)
			} else {
				currentFile.status = FileStatus.success
				console.log('文件访问地址：' + mergeResult.data)
				ElMessage.success(`上传成功，文件地址：${mergeResult.data}`)
				//文件下标偏移
				currentFileIndex++;
				//递归上传下一个文件
				handler()
			}
		}

	})
}

/**
		* 清空列表
		*/
function clearFileHandler() {
	uploadFileList.value = []
	uploadIdInfo.value = null
	currentFileIndex = 0
}

/**
		 * 上传文件列表
		 * @param {*} file 
		 * @param {*} fileList 
		 */
function handleFileChange(file: File, fileList: File[]) {

	//if (!this.beforeUploadVideo(file)) return

	uploadFileList.value = fileList

	uploadFileList.value.forEach((item) => {
		// 初始化自定义属性
		initFileProperties(item)
	})
}

type FileType = {
	chunkList: [],
	status: string,
	progressStatus: string
	uploadProgress: number
}

//初始化文件属性
function initFileProperties(file: FileType) {
	file.chunkList = []
	file.status = FileStatus.wait
	file.progressStatus = 'warning'
	file.uploadProgress = 0
}

/**
		 * 移除文件列表
		 * @param {*} file 
		 * @param {*} fileList 
		 */
function handleRemove(file: File, fileList: File[]) {
	uploadFileList.value = fileList
}

/**
		 * 检查上传文件格式
		 * @param {*} file 
		 */
function beforeUploadVideo(file: File) {
	let type = file.name.substring(file.name.lastIndexOf(".") + 1);
	if (
		[
			"mp4",
			"ogg",
			"flv",
			"avi",
			"wmv",
			"rmvb"
		].indexOf(type) == -1
	) {
		ElMessage.error("请上传正确的视频格式");
		return false;
	}
}

function getNewFileName(file: File) {
	return new Date().getTime() + file.name
	//return md5+"-"+ file.name
}

/**
		 * 分片读取文件 MD5
		 */
function getFileMd5(file: File, callback: any) {
	const blobSlice = File.prototype.slice
	const fileReader = new FileReader()
	// 计算分片数
	const totalChunks = Math.ceil(file.size / chunkSize)
	console.log('总分片数：' + totalChunks)
	let currentChunk = 0
	const spark = new SparkMD5.ArrayBuffer()
	loadNext()
	fileReader.onload = function (e: any) {
		try {
			spark.append(e.target.result)
		} catch (error) {
			console.log('获取Md5错误：' + currentChunk)
		}
		if (currentChunk < totalChunks) {
			currentChunk++
			loadNext()
		} else {
			callback(spark.end(), totalChunks)
		}
	}
	fileReader.onerror = function () {
		console.warn('读取Md5失败，文件读取错误')
	}
	function loadNext() {
		const start = currentChunk * chunkSize
		const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
		// 注意这里的 fileRaw
		fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
	}
}

/**
		 * 文件分片
		 */
function createFileChunk(file: File, size = chunkSize) {
	const fileChunkList = []
	let count = 0
	while (count < file.size) {
		fileChunkList.push({
			file: file.slice(count, count + size),
		})
		count += size
	}
	return fileChunkList
}



/**
		 * 处理即将上传的分片列表，判断是否有已上传的分片，有则从列表中删除
		 */
function processUploadChunkList(chunkList: any[]) {
	const currentFile = uploadFileList.value[currentFileIndex]
	let chunkUploadedList = currentFile.chunkUploadedList
	if (chunkUploadedList === undefined || chunkUploadedList === null || chunkUploadedList.length === 0) {
		return chunkList
	}
	// 
	for (let i = chunkList.length - 1; i >= 0; i--) {
		const chunkItem = chunkList[currentFileIndex]
		for (let j = 0; j < chunkUploadedList.length; j++) {
			if (chunkItem.chunkNumber === chunkUploadedList[j]) {
				chunkList.splice(i, 1)
				break
			}
		}
	}
	return chunkList
}

function uploadChunkBase(chunkList: any[]) {
	let successCount = 0
	let totalChunks = chunkList.length
	return new Promise<void>((resolve, reject) => {
		const handler = () => {
			if (chunkList.length) {

				const chunkItem = chunkList.shift()
				// 直接上传二进制，不需要构造 FormData，否则上传后文件损坏
				axios.put(chunkItem.uploadUrl, chunkItem.chunk.file, {
					// 上传进度处理
					onUploadProgress: checkChunkUploadProgress(chunkItem),
					headers: {
						'Content-Type': 'application/octet-stream'
					}
				}).then(response => {
					if (response.status === 200) {
						console.log('分片：' + chunkItem.chunkNumber + ' 上传成功')
						//如果长度为1，说明是单文件，直接退出
						// if (chunkList.length === 1) {
						// 	return;
						// }
						successCount++
						// 继续上传下一个分片
						handler()
					} else {
						console.log('上传失败：' + response.status + '，' + response.statusText)
					}
				})
					.catch(error => {
						// 更新状态
						console.log('分片：' + chunkItem.chunkNumber + ' 上传失败，' + error)
						// 重新添加到队列中
						chunkList.push(chunkItem)
						handler()
					})
			}
			if (successCount >= totalChunks) {
				resolve()
			}
		}
		// 并发
		for (let i = 0; i < simultaneousUploads.value; i++) {
			handler()
		}
	})
}

function getFileUploadUrls(fileParam: FileParamType) {
	return initUpload(fileParam)
}

function saveFileUploadId(data: any) {
	localStorage.setItem(FILE_UPLOAD_ID_KEY, data)
}

function checkFileUploadedByMd5(md5: string) {
	return new Promise((resolve, reject) => {
		checkUpload(md5).then(response => {
			console.log(response.data);
			resolve(response.data)
		}).catch(error => {
			reject(error)
		})
	})
}

type FileParamType = {
	uploadId: string,
	fileName: string,
	fileMd5: string,
	fileType: string,
}

/**
		 * 合并文件
		 *   uploadId: self.uploadIdInfo.uploadId,
					fileName: currentFile.name,
					//fileMd5: fileMd5,
					bucketName: 'bucket'
		 */
function mergeFile(fileParam: FileParamType) {
	return new Promise((resolve, reject) => {
		mergeUpload(fileParam).then(response => {
			console.log(response.data);
			let data = response.data
			if (!data.data) {
				data.msg = FileStatus.error
				resolve(data)
			} else {
				data.msg = FileStatus.success
				resolve(data)
			}
		})
		// .catch(error => {
		//     self.$message.error('合并文件失败：' + error)
		//     file.status = FileStatus.error
		//     reject()
		// })
	})
}

/**
 * 检查分片上传进度
 */
function checkChunkUploadProgress(item: any) {
	return (p: any) => {
		item.progress = parseInt(String((p.loaded / p.total) * 100))
		updateChunkUploadStatus(item)
	}
}

function updateChunkUploadStatus(item: any) {
	let status = FileStatus.uploading
	let progressStatus = 'normal'
	if (item.progress >= 100) {
		status = FileStatus.success
		progressStatus = 'success'
	}
	let chunkIndex = item.chunkNumber - 1
	let currentChunk = uploadFileList.value[currentFileIndex].chunkList[chunkIndex]
	// 修改状态
	currentChunk.status = status
	currentChunk.progressStatus = progressStatus
	// 更新状态
	uploadFileList.value[currentFileIndex] = currentChunk
	// 获取文件上传进度
	getCurrentFileProgress()
}



function getCurrentFileProgress() {
	const currentFile = uploadFileList.value[currentFileIndex]
	if (!currentFile || !currentFile.chunkList) {
		return
	}
	const chunkList = currentFile.chunkList
	const uploadedSize = chunkList.map((item: any) =>
		item.chunk.file.size * item.progress
	).reduce((acc: number, cur: number) => acc + cur)
	// 计算方式：已上传大小 / 文件总大小
	let progress = parseInt((uploadedSize / currentFile.size).toFixed(2))
	currentFile.uploadProgress = progress
	//$set(uploadFileList.value, currentFile)
	
}


function transformByte(size: number) {
	if (!size) {
		return '0B'
	}
	const unitSize = 1024
	if (size < unitSize) {
		return size + ' B'
	}
	// KB
	if (size < Math.pow(unitSize, 2)) {
		return (size / unitSize).toFixed(2) + ' K';
	}
	// MB
	if (size < Math.pow(unitSize, 3)) {
		return (size / Math.pow(unitSize, 2)).toFixed(2) + ' MB'
	}
	// GB
	if (size < Math.pow(unitSize, 4)) {
		return (size / Math.pow(unitSize, 3)).toFixed(2) + ' GB';
	}
	// TB
	return (size / Math.pow(unitSize, 4)).toFixed(2) + ' TB';
}

</script>

<template>
	<div class="container">
		<div style="display:none;">
			<video width="320" height="240" controls id="upvideo">
			</video>
		</div>
		<h2>上传示例</h2>
		<el-upload class="upload-demo" ref="upload" action="https://jsonplaceholder.typicode.com/posts/"
			:on-remove="() => handleRemove" :on-change="() => handleFileChange" :file-list="uploadFileList"
			:show-file-list="false" :auto-upload="false" multiple>
			<el-button slot="trigger" type="primary" plain>选择文件</el-button>
			<el-button style="margin-left: 5px;" type="success" @click="handler" plain>上传</el-button>
			<el-button type="danger" @click="clearFileHandler" plain>清空</el-button>
		</el-upload>

		<img :src="imgDataUrl" v-show="imgDataUrl" />
		<!-- 文件列表 -->
		<div class="file-list-wrapper">
			<el-collapse>
				<el-collapse-item v-for="(item, index) in uploadFileList" :key="index">
					<template slot="title">
						<div class="upload-file-item">
							<div class="file-info-item file-name" :title="item.name">{{ item.name }}</div>
							<div class="file-info-item file-size">{{ transformByte(item.size) }}</div>
							<div class="file-info-item file-progress">
								<span class="file-progress-label"></span>
								<el-progress :percentage="item.uploadProgress" class="file-progress-value" />
							</div>
							<div class="file-info-item file-size"><span></span>
								<el-tag v-if="item.status === '等待上传'" type="info">等待上传</el-tag>
								<el-tag v-else-if="item.status === '校验MD5'" type="warning">校验MD5</el-tag>
								<el-tag v-else-if="item.status === '正在上传'">正在上传</el-tag>
								<el-tag v-else-if="item.status === '上传成功'" type="success">上传完成</el-tag>
								<el-tag v-else type="danger">上传错误</el-tag>
							</div>
						</div>
					</template>
					<div class="file-chunk-list-wrapper">
						<!-- 分片列表 -->
						<el-table :data="item.chunkList" max-height="400" style="width: 100%">
							<el-table-column prop="chunkNumber" label="分片序号" width="180">
							</el-table-column>
							<el-table-column prop="progress" label="上传进度">
								<template v-slot="{ row }">
									<el-progress v-if="!row.status || row.progressStatus === 'normal'" :percentage="row.progress" />
									<el-progress v-else :percentage="row.progress" :status="row.progressStatus" :text-inside="true"
										:stroke-width="16" />
								</template>
							</el-table-column>
							<el-table-column prop="status" label="状态" width="180">
							</el-table-column>
						</el-table>
					</div>

				</el-collapse-item>
			</el-collapse>
		</div>
	</div>
</template>